/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef HMFS_NODE_H
#define HMFS_NODE_H

#include "hmfs_common.h"
#include "hmfs_data.h"
#include "hmfs_io.h"
#include "hmfs_utils.h"
#include "resize_data.h"

namespace OHOS {
namespace Hmfs {

enum {
    COLD_BIT_SHIFT = 0,
    FSYNC_BIT_SHIFT,
    DENT_BIT_SHIFT,
    OFFSET_BIT_SHIFT
};

#define XATTR_NODE_OFFSET    ((((unsigned int)-1) << OFFSET_BIT_SHIFT) >> OFFSET_BIT_SHIFT)
#define IsFsyncDnode(nodeBlock)    isNode(nodeBlock, FSYNC_BIT_SHIFT)
#define IsDentDnode(nodeBlock)        isNode(nodeBlock, DENT_BIT_SHIFT)

inline int32_t IsInode(struct NodeData *node)
{
    if (node == nullptr) {
        HMFS_ERROR("node is nullptr");
        return -1;
    }
    return ((node)->footer.nid == (node)->footer.ino);
}

inline unsigned long CurrentNatAddr(std::shared_ptr<HmfsSbInfo> sbi, uint32_t start, int *pack)
{
    struct NodeAddressTable *nat = NM_I(sbi);

    unsigned long blockOff = NAT_BLOCK_OFFSET(start);
    int32_t segOff = blockOff >> sbi->logBlksPerSeg;
    unsigned long blockAddr = (unsigned long)(nat->natBlkaddr +
        (segOff << sbi->logBlksPerSeg << 1) +
        (blockOff & ((1 << sbi->logBlksPerSeg) - 1)));
    if (pack) {
        *pack = 1;
    }

    if (HmfsCommon::GetInstance().HmfsTestBit(blockOff, nat->natBitmap)) {
        blockAddr += sbi->blocksPerSeg;
        if (pack) {
            *pack = 2;
        }
    }
    return blockAddr;
}

inline int LookupNatInJournal(std::shared_ptr<HmfsSbInfo> sbi, uint32_t nid, struct natEntry *raw_nat)
{
    struct CurSegmentInfo *curseg = CURSEG_I(sbi, CURSEG_HOT_DATA);
    struct JournalEntry *journal = &curseg->segSumBlk->journal;
    for (int i = 0; i < nats_in_cursum(journal); i++) {
        if (LE32_TO_NATIVE(nid_in_journal(journal, i)) == nid) {
            memcpy(raw_nat, &nat_in_journal(journal, i), sizeof(struct natEntry));
            HMFS_INFO("==> Found nid [0x%x] in nat cache\n", nid);
            return i;
        }
    }
    return -1;
}

inline void GetNatEntry(std::shared_ptr<HmfsSbInfo> sbi, uint32_t nid, struct natEntry *rawNat)
{
    if (LookupNatInJournal(sbi, nid, rawNat) >= 0){
        return;
    }
    struct natBlockData *natBlock = (struct natBlockData *)calloc(BLOCK_SZ, 1);
    ASSERT(natBlock);

    int entryOff = nid % NAT_ENTRY_PER_BLOCK;
    unsigned long blockAddr = CurrentNatAddr(sbi, nid, NULL);

    int ret = HmfsIo::GetInstance().DevReadBlock(natBlock, blockAddr);
    ASSERT(ret >= 0);

    memcpy(rawNat, &natBlock->entries[entryOff], sizeof(struct natEntry));
    free(natBlock);
}

inline void NodeInfoFromRawNat(struct NodeInfo *ni, struct natEntry *rawNat)
{
    ni->ino = LE32_TO_NATIVE(rawNat->inodeNo);
    ni->blockAddr = LE32_TO_NATIVE(rawNat->blockId);
    ni->version = rawNat->version;
}

inline void GetNodeInfo(std::shared_ptr<HmfsSbInfo> sbi, uint32_t nid, struct NodeInfo *ni)
{
    struct natEntry rawNat;
    ni->nid = nid;
    //FSCK command
    // if (c.func == FSCK && F2FS_FSCK(sbi)->nr_nat_entries) {
    //     node_info_from_raw_nat(ni, &(F2FS_FSCK(sbi)->entries[nid]));
    //     if (ni->blkAddr)
    //         return;
    // }
    GetNatEntry(sbi, nid, &rawNat);
    NodeInfoFromRawNat(ni, &rawNat);
}

inline unsigned int ADDRS_PER_PAGE(std::shared_ptr<HmfsSbInfo> sbi, struct NodeData *nodeBlock,
    struct NodeData *inodeBlock)
{
    if (nodeBlock == nullptr || sbi == nullptr || inodeBlock == nullptr) {
        HMFS_ERROR("nodeBlock or sbi or inodeBlock is nullptr");
        return 1;
    }

    uint32_t ino = LE32_TO_NATIVE(nodeBlock->footer.ino);
    if (IsInode(nodeBlock)) {
        return HmfsCommon::GetInstance().AddrsPerInode(&nodeBlock->i);
    }
    unsigned int nblocks = 0;
    if (inodeBlock == nullptr) {
        inodeBlock = static_cast<struct NodeData *>(calloc(BLOCK_SZ, 2));
        ASSERT(inodeBlock);
        struct NodeInfo ni;
        GetNodeInfo(sbi, ino, &ni);
        ASSERT(HmfsIo::GetInstance().DevReadBlock(inodeBlock, ni.blockAddr) >= 0);
        nblocks = HmfsCommon::GetInstance().AddrsPerBlock(&inodeBlock->i);
        free(inodeBlock);
    } else {
        nblocks = HmfsCommon::GetInstance().AddrsPerBlock(&inodeBlock->i);
    }
    return nblocks;
}

inline uint32_t *BlockAddressInINode(struct NodeData *node)
{
    if (node == nullptr) {
        return nullptr;
    }
    return node->i.i_addr + HmfsCommon::GetInstance().GetExtraIsize(&node->i);
}

inline uint32_t *BlockAddressInNode(struct NodeData *node)
{
    if (node == nullptr) {
        return nullptr;
    }
    return IsInode(node) ? BlockAddressInINode(node) : node->dn.addr;
}

inline uint32_t DatablockAddr(struct NodeData *nodePage, unsigned int offset)
{
    if (nodePage == nullptr) {
        return 0;
    }
    uint32_t *addr_array = BlockAddressInNode(nodePage);
    return LE32_TO_NATIVE(addr_array[offset]);
}

enum {
    ALLOC_NODE,    /* allocate a new node page if needed */
    LOOKUP_NODE,    /* lookup up a node without readahead */
    LOOKUP_NODE_RA,
};

inline unsigned int ofs_of_node(struct NodeData *nodeBlock)
{
    if (nodeBlock == nullptr) {
        HMFS_ERROR("node_blk is nullptr");
        return 0;
    }
    unsigned flag = LE32_TO_NATIVE(nodeBlock->footer.flag);
    return flag >> OFFSET_BIT_SHIFT;
}

inline int IS_DNODE(struct NodeData *nodePage)
{
    if (nodePage == nullptr) {
        return -1;
    }
    unsigned int ofs = ofs_of_node(nodePage);

    if (ofs == 3 || ofs == 4 + NIDS_PER_BLOCK || ofs == 5 + 2 * NIDS_PER_BLOCK) {
        return 0;
    }

    if (ofs >= 6 + 2 * NIDS_PER_BLOCK) {
        ofs -= 6 + 2 * NIDS_PER_BLOCK;
        if (!((long int)ofs % (NIDS_PER_BLOCK + 1))) {
            return 0;
        }
    }
    return 1;
}

inline uint32_t InoOfNode(struct NodeData *nodeBlock)
{
    if (nodeBlock == nullptr) {
        return 0;
    }
    return LE32_TO_NATIVE(nodeBlock->footer.ino);
}

inline unsigned int OfsOfNode(struct NodeData *nodeBlock)
{
    if (nodeBlock == nullptr) {
        return 0;
    }
    unsigned flag = LE32_TO_NATIVE(nodeBlock->footer.flag);
    return flag >> OFFSET_BIT_SHIFT;
}

inline uint64_t GetNodeCheckPointVersion(struct NodeData *nodeBlock)
{
    if (nodeBlock == nullptr) {
        return 0;
    }
    return LE64_TO_NATIVE(nodeBlock->footer.cpVer);
}

inline unsigned long long GetCheckPointVersion(struct CheckPointData *cp)
{
    if (cp == nullptr) {
        return 0;
    }
    return LE64_TO_NATIVE(cp->cpVersion);
}

inline bool IsSetCheckPointFlagd(struct CheckPointData *cp, unsigned int f)
{
    if (cp == nullptr) {
        return false;
    }
    unsigned int flag = LE32_TO_NATIVE(cp->cpFlags);
    return flag & f ? 1 : 0;
}

inline uint64_t cur_cp_crc(struct CheckPointData *cp)
{
    if (cp == nullptr) {
        return 0;
    }
    size_t offset = LE32_TO_NATIVE(cp->checksumOffset);
    return LE32_TO_NATIVE(*((uint32_t *)((unsigned char *)cp + offset)));
}

inline bool IsRecoverableDnode(std::shared_ptr<HmfsSbInfo> sbi, struct NodeData *nodeBlock)
{
    if (nodeBlock == nullptr || sbi == nullptr) {
        HMFS_ERROR("nodeBlock or sbi is nullptr");
        return false;
    }

    struct CheckPointData *checkPointData = F2FS_CKPT(sbi);
    uint64_t checkpointVersion = GetCheckPointVersion(checkPointData);

    /* Don't care crc part, if fsck.f2fs sets it. */
    if (IsSetCheckPointFlagd(checkPointData, CP_NOCRC_RECOVERY_FLAG)) {
        return (checkpointVersion << 32) == (GetNodeCheckPointVersion(nodeBlock) << 32);
    }

    if (IsSetCheckPointFlagd(checkPointData, CP_CRC_RECOVERY_FLAG)) {
        checkpointVersion |= (cur_cp_crc(checkPointData) << 32);
    }

    return checkpointVersion == GetNodeCheckPointVersion(nodeBlock);
}

inline uint32_t NextBlkAddrOfNode(struct NodeData *nodeBlock)
{
    if (nodeBlock == nullptr) {
        return 0;
    }
    return LE32_TO_NATIVE(nodeBlock->footer.nextBlkaddr);
}

inline int isNode(struct NodeData *nodeBlock, int type)
{
    if (nodeBlock == nullptr) {
        return -1;
    }
    return LE32_TO_NATIVE(nodeBlock->footer.flag) & (1 << type);
}

inline unsigned int StartBidxOfNode(unsigned int nodeOfs, struct NodeData *nodeBlk)
{
    unsigned int indirectBlks = 2 * NIDS_PER_BLOCK + 4;
    unsigned int bidx;
    if (nodeOfs == 0) {
        return 0;
    }
    if (nodeOfs <= 2) {
        bidx = nodeOfs - 1;
    } else if (nodeOfs <= indirectBlks) {
        int dec = (nodeOfs - 4) / (NIDS_PER_BLOCK + 1);
        bidx = nodeOfs - 2 - dec;
    } else {
        int dec = (nodeOfs - indirectBlks - 3) / (NIDS_PER_BLOCK + 1);
        bidx = nodeOfs - 5 - dec;
    }
    return bidx * HmfsCommon::GetInstance().AddrsPerBlock(&nodeBlk->i) + HmfsCommon::GetInstance().AddrsPerInode(&nodeBlk->i);
}

} // namespace Hmfs
} // namespace OHOS
#endif
